package com.example.big_upload.service;


import com.example.big_upload.model.RespBean;
import com.example.big_upload.utils.CheckUtil;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.util.ArrayList;
import java.util.List;

@Service
public class BigUploadService {

    public static final String BASE_PATH = "./upload";
    // 记录上传了哪些分片文件
    public static final String CONF = "conf";
    // 标志文件是否上传完成的文件
    public static final String FLAG = "flag";
    // 分片大小
    public static final Integer CHUNK_SIZE = 100 * 1024 * 1024;

    /**
     * 分片上传第一步 检查该文件是否上传
     * @param token 唯一标识
     * @param total 分片总数
     */
    public RespBean checkMerge(String token, Integer total) {
        // 1. 先判断 flag 文件是否存在，如果存在说明 上传的文件已经存在，直接返回上传成功
        String flag = getFlagFileName(token);
        if (new File(flag).exists()){
            return RespBean.ok("上传成功");
        }
        // 2. 如果不存在，则判断配置文件是否存在
        List<Integer> completedList = new ArrayList<>();//已经完成的分片文件
        String confFileName = getConfFileName(token);
        File confFile = new File(confFileName);
        try {
            // 3. 如果不存在，就说明第一次上传，先创建总分片数大小的 conf 文件，如果存在，不做处理
            if (!confFile.exists()) {
                if (!confFile.getParentFile().exists()) {
                    confFile.getParentFile().mkdirs();
                }
                try (RandomAccessFile raFile = new RandomAccessFile(confFile, "rw")) {
                    raFile.setLength(total);// 设置分片的总数量，即 conf 文件的大小，数据默认为 0
                }
            }
            // 4. 然后直接从 conf 中获取 已完成的分片文件返回
            byte[] bytes = getAllByteArray(confFile);
            for (int i = 0; i < bytes.length; i++) {
                if (Byte.MAX_VALUE == bytes[i]) {
                    completedList.add(i);
                }
            }
            return RespBean.ok(201, "上传中", completedList);
        } catch (IOException e) {
            e.printStackTrace();
            return RespBean.error("系统异常");
        }
    }

    /**
     * 上传分片文件
     * @param file 分片文件
     * @param token 分片文件的唯一标识，用来校验分片文件完整性
     * @param number 分片序号
     * @param parentToken 合并文件唯一标识，用来获取文件路径
     * @param fileName 合并文件名
     */
    public RespBean uploadPart(MultipartFile file, String token, Integer number, String parentToken, String fileName) {
        try {
            // 1. 先根据 唯一标识 检查前端传来的分片文件是否完成
            InputStream inputStream = file.getInputStream();
            byte[] bytes = getAllByteArray(inputStream);
            String checkToken = CheckUtil.generateSHA256(bytes);
            // 2. 如果不完整，返回错误提示 文件不完整
            if (!checkToken.equals(token)) {
                return RespBean.error("分片文件不完整");
            }
            // 3. 如果完整，根据分片序号和每片分块大小通过 RandomAccessFile 指定偏移量写入对应的分片文件
            File destFile = getFileByName(getDestFileName(parentToken,fileName));
            try (RandomAccessFile accessFile = new RandomAccessFile(destFile, "rw")) {
                accessFile.seek((long) number * CHUNK_SIZE);
                accessFile.write(bytes);
            }
            // 4. 然后将 conf 文件 的对应位置修改为 127
            String confFileName = getConfFileName(parentToken);
            try (RandomAccessFile randomAccessFile = new RandomAccessFile(confFileName, "rw")) {
                randomAccessFile.seek(number);
                randomAccessFile.write(Byte.MAX_VALUE);
            }
            // 5. 最后返回 分片文件上传成功
            return RespBean.ok("分片文件上传成功 ");
        } catch (IOException e) {
            e.printStackTrace();
            return RespBean.error("系统异常");
        }
    }

    /**
     *
     * @param token 合并文件的唯一标识
     * @param fileName 合并的文件名
     */
    public RespBean merge(String token, String fileName) {
        try {
            // 1. 先校验 conf 文件的数据是否全部为 127
            String confFileName = getConfFileName(token);
            byte[] confBytes = getAllByteArray(getFileByName(confFileName));
            for (byte confByte : confBytes) {
                // 2. 如果不是，则说明还有分片文件没有上传成功，返回 存在未完成的分片文件
                if (0 == confByte) {
                    return RespBean.error("存在未完成的分片文件");
                }
            }
            // 3. 如果是，则说明分片文件已经全部上传完成，然后进行合并操作，合并完成之后计算合并文件的 sha256，与前端传来的比对
            //   （利用 RandomAccessFile 在上传分片文件的时候就自动合并了，只需要 校验文件完整性即可）
            File destFile = getFileByName(getDestFileName(token,fileName));
            String mergeToken = CheckUtil.generateSHA256(new FileInputStream(destFile));
            // 4. 如果比对失败，则返回 文件不完整
            if (!mergeToken.equals(token)) {
                return RespBean.error("文件不完整");
            }
            // 5. 比对成功，然后创建 flag 文件，表示该文件已上传
            String flagName = getFlagFileName(token);
            File flagFile = getFileByName(flagName);
            if (!flagFile.exists() && !getFileByName(flagName).createNewFile()){
                throw new IOException("文件创建失败");
            }
            return RespBean.ok("文件上传成功");
        } catch (IOException e) {
            e.printStackTrace();
            return RespBean.error("系统异常");
        }
    }

    public static String getConfFileName(String token) {
        return BASE_PATH + File.separator + token + File.separator + CONF;
    }

    public static String getFlagFileName(String token) {
        return BASE_PATH + File.separator + token + File.separator + FLAG;
    }

    public static String getDestFileName(String token,String fileName) {
        return BASE_PATH + File.separator + token + File.separator + fileName;
    }

    public static File getFileByName(String name) {
        File file = new File(name);
        if (!file.getParentFile().exists()) {
            file.getParentFile().mkdirs();
        }
        return file;
    }

    public static byte[] getAllByteArray(File file) throws IOException {
        byte[] buf = new byte[8192];
        int len = 0;
        try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
             ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
            while ((len = bis.read(buf)) != -1) {
                baos.write(buf, 0, len);
            }
            return baos.toByteArray();
        }
    }

    public static byte[] getAllByteArray(InputStream inputStream) throws IOException {
        byte[] buf = new byte[8192];
        int len = 0;
        try (BufferedInputStream bis = new BufferedInputStream(inputStream);
             ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
            while ((len = bis.read(buf)) != -1) {
                baos.write(buf, 0, len);
            }
            return baos.toByteArray();
        }
    }

    /**
     * 直接上传
     * @param file 文件
     * @param token 唯一标识
     */
    public RespBean upload(MultipartFile file, String token) {
        String fileName = "E:/java_study/javaguide/javase/java-io/demo03_big_upload/src/main/resources/upload2/" + file.getOriginalFilename();
        File destFile = getFileByName(fileName);
        try {
            file.transferTo(destFile);
            String s = CheckUtil.generateSHA256(new FileInputStream(fileName));
            if (s.equals(token)) {
                return RespBean.ok("上传成功");
            }
            return RespBean.ok("文件不完整");
        } catch (IOException e) {
            e.printStackTrace();
            return RespBean.error("系统异常");
        }
    }
}
